#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-2.0-only
# Copyright (c) 2022 Intel Corporation.

# shellcheck source=/dev/null
source .env

readonly PASS_CODE=0
readonly BLOCK_CODE=2
readonly SKIP_CODE=4
readonly NA_CODE=32
PASS="PASS"
BLOCK="BLOCK"
SKIP="SKIP"
FAIL="FAIL"
NA="NA"
DEP_EXIT=$PASS_CODE

START_TIME=""
START_TIME_SEC=""
END_TIME_SEC=""
SUMMRY_LOG=""
readonly TESTS_SERVER="tests-server"
TEST_FILES=""
SUB_FOLDERS=""
TEST_LIST_FILE="/tmp/tests"
OLD_IFS=""

# For dependence check parameters, set skip for unknow staus as default
readonly MARK_HW_DEP="@hw_dep:"
readonly MARK_OTHER_DEP="@other_dep:"
readonly MARK_WARN_DEP="@other_warn:"
export HW_DEP="$SKIP_CODE"
export OTHER_DEP="$SKIP_CODE"
export OTHER_WARN="$SKIP_CODE"
REASON=""
DEP_LOG="/tmp/lkvs_dependence.log"

usage() {
  cat << _EOF
Usage: ${0##*/} [-f CMDFILES] [-c CMDLINE] [-o LOGFILE]
  -f CMDFILES   execute user defined list of tests in files separated by ','
  -d DEPENDENCE_FILE check dependence for specified feature tests
  -t TYPE_FILE  check test type like tests-server|tests-client
  -c CMDLINE    execute test case
  -s SCENARIO_FOLDER  all tests-* files under target folder
  -o LOGFILE    redirect output of tests to file

Examples:
  ./runtests -f cet/tests
  ./runtests -f cet/tests -o cet_tests.log
  ./runtests -t tests-client  // type like tests-client|tests for all subfolders
  ./runtests -t tests-server  // type like tests-server|tests for all subfolders
  ./runtests -c ./cet/quick_test -o cet_quick_test.log
  ./runtests -s ../scenario/emr-oe  // scenario test files folder
  ./runtests -d cet/tests     // check cet dependence
  ./runtests -d tests-server  // check tests-server/tests for all subfolders dependence
_EOF
}

append_log() {
  local info=$1
  local log_file=$2

  if [[ -z "$log_file" ]]; then
    echo -e "$info"
  else
    echo -e "$info" | tee -a "$log_file"
  fi
}

err() {
  echo -e "\n$*" >&2
  exit 1
}

check_test_file_legal() {
  local cmdfile=$1

  if [[ ! -f "$cmdfile" ]]; then
    append_log "WARNING: $cmdfile not found!" "$LOGFILE"
    return "$NA_CODE"
  fi

  [[ $(which file) ]] && {
    file_type=""
    file_type=$(file "$cmdfile")
    [[ "$file_type" == *"text"* ]] || [[ "$file_type" == *"link"* ]] || {
      append_log "WARNING: -f $cmdfile is not a text or link type, it's type:|$file_type|" "$LOGFILE"
      append_log "Please choose the correct tests file." "$LOGFILE"
      return "$NA_CODE"
    }
  }

  return 0
}

init_dep_log() {
  local dep_log=$1

  cat /dev/null > "$dep_log"
  {
    echo "-----------------------------------------------------------------"
    printf "%-35s%-13s%-16s%-13s%-20s\n" "Feature" "|HW_support" "|Other_support" \
      "|Other_WARN" "|Reason"
    printf "%-35s%-13s%-16s%-13s%-20s\n" "-------" "-----------" "--------------" \
      "-----------" "-------"
  } >> "$dep_log"
}

check_dep_cmd() {
  local dep_info=$1
  local dep=$2
  local dep_cmd dep_reason dep_app app

  dep_cmd=$(echo "$dep_info" | awk -F "@" '{print $1}')
  dep_reason=$(echo "$dep_info" | awk -F "@" '{print $2}')

  # Avoid dependency check failure due to uncompiled app
  dep_app=$(echo "$dep_info" | awk -F " " '{print $1}')
  [[ "$dep_app" == *".sh" ]] || {
    app=$(which "$dep_app" 2>/dev/null)
    [[ -z "$app" ]] && {
      REASON="The dependent app:$dep_app was not found and make under lkvs/BM first!"
      return "$BLOCK_CODE"
    }
  }

  # Execute ret not zero, will return below error code as failed
  eval "$dep_cmd" 1>/dev/null || {
    if [[ -z "$dep_reason" ]]; then
      REASON="${dep} ${dep_cmd} failed"
    else
      REASON="${dep}${dep_reason}"
    fi
    return "$BLOCK_CODE"
  }

  return 0
}

check_dep_info() {
  local dep_infos=$1
  local subfolder=$2
  local dep=$3
  local ret=""

  if [[ -z "$dep_infos" ]]; then
    REASON="No dependence for $subfolder"
    return "$SKIP_CODE"
  fi

  IFS=$'\n'
  for dep_info in $dep_infos; do
    check_dep_cmd "$dep_info" "$dep" || {
      ret=$?
      IFS="$(printf ' \t\n')"
      return "$ret"
    }
  done
  IFS="$(printf ' \t\n')"
}

# Don't input any other echo in it
get_ret_status() {
  local ret=$1

  case $ret in
    "$PASS_CODE") #0
      echo "$PASS"
      ;;
    "$BLOCK_CODE") #2
      echo "$BLOCK"
      ;;
    "$SKIP_CODE") #4
      echo "$SKIP"
      ;;
    *)
      echo "$NA"
      ;;
  esac
}

check_dep_feature() {
  local cmdfile=$1
  local subfolder=""
  local hw_dep_info other_dep_info other_warn_info
  local hw_sta other_sta warn_sta

  if [[ "$cmdfile" == *"/"* ]]; then
    subfolder=${cmdfile%/*}
  else
    append_log "cmdfile:$cmdfile(no '/') is not in a subfolder, skip dependence check!" "$LOGFILE"
    return "$SKIP_CODE"
  fi

  hw_dep_info=$(grep "$MARK_HW_DEP" "$cmdfile" | awk -F "$MARK_HW_DEP" '{print $2}' | sed -e 's/^ *//g')
  other_dep_info=$(grep "$MARK_OTHER_DEP" "$cmdfile" | awk -F "$MARK_OTHER_DEP" '{print $2}' | sed -e 's/^ *//g')
  other_warn_info=$(grep "$MARK_WARN_DEP" "$cmdfile" | awk -F "$MARK_WARN_DEP" '{print $2}' | sed -e 's/^ *//g')

  check_dep_info "$hw_dep_info" "$cmdfile" "$MARK_HW_DEP"
  ret=$?
  hw_sta=$(get_ret_status "$ret")
  [[ "$ret" == "$NA_CODE" || "$ret" == "$BLOCK_CODE" ]] && {
    printf "%-35s%-13s%-16s%-13s%-20s\n" "$cmdfile" "|$hw_sta" "|$SKIP" "|$SKIP" \
      "|$REASON" >> "$DEP_LOG"
    return "$ret"
  }

  check_dep_info "$other_dep_info" "$cmdfile" "$MARK_OTHER_DEP"
  ret=$?
  other_sta=$(get_ret_status "$ret")
  [[ "$ret" == "$NA_CODE" || "$ret" == "$BLOCK_CODE" ]] && {
    printf "%-35s%-13s%-16s%-13s%-20s\n" "$cmdfile" "|$hw_sta" "|$other_sta" "|$SKIP" \
      "|$REASON" >> "$DEP_LOG"
    return "$ret"
  }

  # It's a warning, will not block the subfolder test cases
  check_dep_info "$other_warn_info" "$cmdfile" "$MARK_WARN_DEP"
  ret=$?
  warn_sta=$(get_ret_status "$ret")
  # warn_sta does not set the block, just NA to fail in some cases of this feature
  [[ "$ret" == "$BLOCK_CODE" ]] && warn_sta="$NA"

  [[ -z "$REASON" || "$ret" == "$PASS_CODE" ]] && REASON="No dependence for $cmdfile"
  printf "%-35s%-13s%-16s%-13s%-20s\n" "$cmdfile" "|$hw_sta" "|$other_sta" "|$warn_sta" \
    "|$REASON" >> "$DEP_LOG"
  return 0
}

runtest() {
  local cmdline=$1
  local logfile=$2
  local subfolder=$3
  local start
  local stop
  local duration
  local code
  local result
  local case_result=""

  append_log "<<<test start - '$cmdline'>>" "$logfile"

  if [[ -z "$subfolder" ]]; then
    echo "LKVS tests: $cmdline" >> /dev/kmsg
  else
    echo "LKVS tests: ${subfolder}/${cmdline}" >> /dev/kmsg
  fi

  set -o pipefail
  start=$(date +%s.%3N)

  if [[ -n "$logfile" ]]; then
    eval "$cmdline |& tee -a $logfile" &
  else
    eval "$cmdline" &
  fi

  wait $!
  code=$?

  stop=$(date +%s.%3N)
  duration=$(printf '%.3f' "$(bc <<< "$stop-$start")")
  set +o pipefail

  case $code in
    "$PASS_CODE")
      result="$PASS"
      ;;
    "$BLOCK_CODE")
      result="$BLOCK"
      ;;
    "$NA_CODE")
      result="$NA"
      ;;
    *)
      result="$FAIL"
      ;;
  esac

  append_log "<<<test end, result: $result, duration: ${duration}s>>\n" "$logfile"
  case_result=$(printf "[RESULT]%-68s%-11s%-12s%-8s" "[$cmdline]" " [$result]" "[$code]" \
    "[${duration}s]")
  echo "$case_result" >> "$SUMMRY_LOG"
}

runcmdfile() {
  local cmdfile=$1
  local logfile=$2
  local subfolder=""
  local file_type=""
  local lines=""
  local line=""

  if [[ "$cmdfile" == *"/"* ]]; then
    subfolder=${cmdfile%/*}
  else
    append_log "cmdfile:$cmdfile(no '/') is not in a subfolder!" "$logfile"
  fi

  grep -v "^#.*" "$cmdfile" | grep -v "^$" > "$TEST_LIST_FILE"
  lines=$(cat $TEST_LIST_FILE)
  OLD_IFS="$IFS"
  IFS=$'\n'
  for line in $lines; do
    runtest "$line" "$logfile" "$subfolder"
  done
  IFS="$OLD_IFS"
}

prepare_files_list() {
  local test_file_type=$1
  local sub_folder=""

  for sub_folder in $SUB_FOLDERS; do
    # Remove the / in the end
    sub_folder="${sub_folder%/}"
    [[ "$sub_folder" == "common" || "$sub_folder" == "tools" ]] && continue

    if [[ -e "${sub_folder}/${test_file_type}" ]]; then
      if [[ -z "$TEST_FILES" ]]; then
        TEST_FILES="${sub_folder}/${test_file_type}"
      else
        TEST_FILES="${TEST_FILES},${sub_folder}/${test_file_type}"
      fi
    elif [[ -e "${sub_folder}/tests" ]]; then
      if [[ -z "$TEST_FILES" ]]; then
        TEST_FILES="${sub_folder}/tests"
      else
        TEST_FILES="${TEST_FILES},${sub_folder}/tests"
      fi
    elif [[ -e "${sub_folder}/${TESTS_SERVER}" ]]; then
      if [[ -z "$TEST_FILES" ]]; then
        TEST_FILES="${sub_folder}/${TESTS_SERVER}"
      else
        TEST_FILES="${TEST_FILES},${sub_folder}/${TESTS_SERVER}"
      fi
    else
      append_log "WARNING: No $test_file_type or tests, $TESTS_SERVER under $sub_folder folder." "$LOGFILE"
    fi
  done
}

list_all_test_files() {
  local test_file_type=$1

  # Don't quote */, otherwise it could not list all folders.
  SUB_FOLDERS=$(ls -1 -d -- */)

  prepare_files_list "$test_file_type"
  CMDFILES="$TEST_FILES"
  append_log "Test files: $CMDFILES" "$LOGFILE"
}

show_result() {
  local execution_time=""

  END_TIME_SEC=$(date +%s)
  # Will record execution time 1s if it's less than 1s.
  execution_time=$((END_TIME_SEC - START_TIME_SEC + 1))
  # Add the following print for the end of the case summary.
  echo "--------------------------------------------------------" >> "$SUMMRY_LOG"
  echo "LKVS log $LOGFILE as above. Execution time:${execution_time}s." >> "$SUMMRY_LOG"
  [[ -z "$LOGFILE" ]] || cat "$SUMMRY_LOG" >> "$LOGFILE"
  cat "$SUMMRY_LOG"

  echo "The dependency info $DEP_LOG is as follows:"
  cat "$DEP_LOG"
}

# runtests_cleanup to show test result. TODO: restore the env if needed
runtests_cleanup() {
  show_result
  exit 0
}

run_tests() {
  if [[ -z "$CMDFILES" ]] && [[ -z "$CMDLINE" ]]; then
    usage
    err "no test to run!"
  fi

  cat /dev/null > "$LOGFILE"

  START_TIME="$(date +%Y-%m-%d_%H-%M-%S)"
  START_TIME_SEC=$(date +%s)
  SUMMRY_LOG="/tmp/lkvs_${START_TIME}_summary.log"
  echo "Test Start Time: $START_TIME" > "$SUMMRY_LOG"
  {
    echo "--------------------------------------------------------"
    printf "%-77s%-10s%-12s%-8s\n" "Testcase" "Result" "Exit Value" "Duration"
    printf "%-77s%-10s%-12s%-8s\n" "--------" "------" "----------" "--------"
  } >> "$SUMMRY_LOG"

  init_dep_log "$DEP_LOG"

  for cmdfile in $(tr "," " " <<< "$CMDFILES"); do
    check_test_file_legal "$cmdfile" || continue

    check_dep_feature "$cmdfile" || {
      append_log "Skip $cmdfile due to dependece, please check $DEP_LOG" "$LOGFILE"
      DEP_EXIT=$BLOCK_CODE
      continue
    }
    append_log "Next run cases from $cmdfile" "$LOGFILE"
    runcmdfile "$cmdfile" "$LOGFILE"
  done

  if [[ -n "$CMDLINE" ]]; then
    runtest "$CMDLINE" "$LOGFILE"
  fi
}

# Default value
: "${LOGFILE:="./lkvs.log"}"
: CMDFILES=""
: CMDLINE=""

while getopts ":o:d:f:t:s:c:h" opt; do
  case "$opt" in
    o)
      LOGFILE=$OPTARG
      ;;
    d)
      init_dep_log "$DEP_LOG"
      CMDFILES=$OPTARG

      # If tests-server type will list tests-server in all subfolders
      [[ "$CMDFILES" == "$TESTS_SERVER" ]] && list_all_test_files "$CMDFILES"

      for cmdfile in $(tr "," " " <<< "$CMDFILES"); do
        check_test_file_legal "$cmdfile" || continue

        check_dep_feature "$cmdfile" || {
          append_log "Skip $cmdfile due to dependece, please check $DEP_LOG" "$LOGFILE"
          DEP_EXIT=$BLOCK_CODE
          continue
        }
      done
      cat "$DEP_LOG"
      exit "$DEP_EXIT"
      ;;
    f)
      CMDFILES=$OPTARG
      ;;
    t)
      # If tests-server type will list tests-server in all subfolders
      [[ "$CMDFILES" == *"-"* ]] && list_all_test_files "$CMDFILES"
      ;;
    s)
      test_files=""

      SCENARIO_FOLDER=$OPTARG
      [[ -d "$SCENARIO_FOLDER" ]] || usage
      test_files=$(find "$SCENARIO_FOLDER" -name "tests-*")
      [[ -z "$test_files" ]] && {
        append_log "No tests-* files under $SCENARIO_FOLDER"
        exit 2
      }
      for cmdfile in $test_files; do
        CMDFILES="$CMDFILES,$cmdfile"
      done
      ;;
    c)
      CMDLINE=$OPTARG
      ;;
    h)
      usage
      exit 0
      ;;
    \?)
      usage
      err "Invalid option: -$OPTARG"
      ;;
    :)
      usage
      err "Option -$OPTARG requires an argument."
      ;;
  esac
done

trap runtests_cleanup SIGTERM SIGINT
run_tests
runtests_cleanup
